/*
 * This file is part of the Sx Framework Library.
 * 
 * Copyright (C) 2013 University of Colorado Denver
 * <min.choi@ucdenver.edu> <shane.transue@ucdenver.edu>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */
#include <sxViewController.h>
#include <sxCamera.h>
#include <sxViewport.h>
#include <sxEditorMouseController.h>
#include <iostream>
#include <gl/glut.h>

/*
 * Default projection and perspective for the camera provided by this
 * controller.
 */
const Sx::Math::Projection DEFAULT_PROJECTION = Sx::Math::PERSPECTIVE;
const Sx::Math::Perspective DEFAULT_PERSPECTIVE = Sx::Math::THIRD_PERSON;

const float DEFAULT_VERTICAL_ROTATION = 0.5236f;
const float DEFAULT_HORIZONTAL_ROTATION = 0.7853981f;
const float DEFAULT_SENSITIVITY_X = 0.01f;
const float DEFAULT_SENSITIVITY_Y = 0.01f;

Sx::Interface::ViewController::ViewController(Viewport* viewport, const std::shared_ptr<RendererImplementation>& cameraRenderer) : 
	ViewportController(viewport) {

	//--------------------------------------------------------------------------
	// Construct a default camera archetype. The default archetype creates a 
	// simple 3rd person camera.
	//--------------------------------------------------------------------------
	this->camera = std::make_shared<Camera_Archetype>(cameraRenderer);

	//--------------------------------------------------------------------------
	// The default mouse controller provides refined commands for viewing and
	// editing virtual scenes.
	//--------------------------------------------------------------------------
	this->mouseController = std::make_shared<EditorMouseController>();
}

Sx::Interface::ViewController::~ViewController() {}

/*
 * Helper function that sets the default settings of a camera.
 */
void SetDefaultCameraPosition(const std::shared_ptr<Sx::Graphics::Archetypes::Objects::Camera_Archetype>& camera) {
	if ( camera == nullptr ) return;

	if ( camera->getObject()->getPerspective() == Sx::Math::THIRD_PERSON ) {
		camera->getObject()->setVRadians(DEFAULT_VERTICAL_ROTATION);
		camera->getObject()->setHRadians(DEFAULT_HORIZONTAL_ROTATION);
		camera->getObject()->setPosition(0.0f, 0.0f, 0.0f);
	}
	else camera->getObject()->setHRadians(-DEFAULT_HORIZONTAL_ROTATION * 2);
}

bool Sx::Interface::ViewController::initialize() {
	ViewportController::initialize();

	SetDefaultCameraPosition(this->camera);
	this->camera->getObject()->setSensitivityX(DEFAULT_SENSITIVITY_X);
	this->camera->getObject()->setSensitivityY(DEFAULT_SENSITIVITY_Y);

	this->camera->construct();

	return true;
}

void Sx::Interface::ViewController::update(float dt) {
	return;
}

bool Sx::Interface::ViewController::preRender() const {
	if ( this->camera == nullptr ) return false;

	ViewportController::preRender();
	this->camera->update(0.0f);
	this->camera->render();

	return true;
}

void Sx::Interface::ViewController::render() const {
	this->preRender();
	glColor3f(0.8f, 0.8f, 0.8f);
	glutWireSphere(1.0f, 20, 20); // TESTING.. normally view controller doesnt render anything
	this->postRender();
}

bool Sx::Interface::ViewController::postRender() const {
	return ViewportController::postRender();
}

void Sx::Interface::ViewController::resize(int width, int height) {
	if ( this->camera == nullptr ) return;

	this->camera->getObject()->setSize(width, height);
	this->camera->update(0.0f);
}

void Sx::Interface::ViewController::setDisplayMode(DisplayMode mode) {
	return;
}

void Sx::Interface::ViewController::setPerspective(Math::Perspective perspective) {
	if ( this->camera == nullptr ) {
		std::cout << "[ViewController:setPerspective] Warning: Cannot set perspective for null camera." << std::endl;
		return;
	}
	this->camera->getObject()->setPerspective(perspective);
	SetDefaultCameraPosition(this->camera);
}

void Sx::Interface::ViewController::setProjection(Math::Projection projection) {
	if ( this->camera == nullptr ) {
		std::cout << "[ViewController:setProjection] Warning: Cannot set projection for null camera." << std::endl;
		return;
	}

	this->camera->getObject()->setProjection(projection);
}

void Sx::Interface::ViewController::setView(Math::View view) {
	if ( this->camera == nullptr ) {
		std::cout << "[ViewController:setView] Warning: Cannot set camera view for null camera." << std::endl;
		return;
	}

	this->camera->getObject()->setView(view);
}

void Sx::Interface::ViewController::setCameraPosition(float x, float y, float z) {
	if ( this->camera == nullptr ) return;
	this->camera->getObject()->setPosition(x, y, z);
}

void Sx::Interface::ViewController::setCameraPosition(const Eigen::Vector3f& position) {
	if ( this->camera == nullptr ) return;
	this->camera->getObject()->setPosition(position);
}

void Sx::Interface::ViewController::resetView() {
	if ( this->camera == nullptr ) return;
	this->camera->getObject()->setPosition(0.0f, 0.0f, 0.0f);
	this->camera->getObject()->resetRotation();
}

Sx::Math::Perspective Sx::Interface::ViewController::getPerspective() const {
	return this->camera->getObject()->getPerspective();
}

Sx::Math::Projection Sx::Interface::ViewController::getProjection() const {
	return this->camera->getObject()->getProjection();
}

Sx::Math::View Sx::Interface::ViewController::getView() const {
	return this->camera->getObject()->getView();
}

void Sx::Interface::ViewController::setMouseController(const std::shared_ptr<MouseMovementController>& mouseController) {
	this->mouseController = mouseController;
}

void Sx::Interface::ViewController::mousePressEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	this->mousePosLast.x() = x;
	this->mousePosPress.x() = x;
	this->mousePosLast.y() = y;
	this->mousePosPress.y() = y;
}

void Sx::Interface::ViewController::mouseReleaseEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	this->mousePosRelease.x() = x;
	this->mousePosRelease.y() = y;
}

void Sx::Interface::ViewController::mouseClickEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {}

void Sx::Interface::ViewController::mouseMoveEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	this->mousePosCur.x() = x;
	this->mousePosCur.y() = y;

	//--------------------------------------------------------------------------
	// Calculate the x and y difference between the last mouse position and the
	// current mouse position. This information can be used to determine how
	// far the mouse has been dragged. Most camera operations depend on this
	// information.
	//--------------------------------------------------------------------------
	this->positionDifference.x() = this->mousePosLast.x() - this->mousePosCur.x();
	this->positionDifference.y() = this->mousePosLast.y() - this->mousePosCur.y();

	//--------------------------------------------------------------------------
	// Based on the button combination that is pressed, dispatch the mouse
	// event to the mouse controller so that the proper mouse controls can
	// be provided to the user. This allows the mouse controls of any controller
	// to be changed just by providing a new mouse controller to a viewport
	// controller.
	//--------------------------------------------------------------------------
	if ( button == LEFT_MOUSE_BUTTON )
		this->mouseController->onMouse_Left_Drag(this->camera->getObject().get(), this->positionDifference.x(), this->positionDifference.y(), modifierKey);
	else if ( button == RIGHT_MOUSE_BUTTON )
		this->mouseController->onMouse_Right_Drag(this->camera->getObject().get(), this->positionDifference.x(), this->positionDifference.y(), modifierKey);
	else if ( button == MIDDLE_MOUSE_BUTTON )
		this->mouseController->onMouse_Middle_Drag(this->camera->getObject().get(), this->positionDifference.x(), this->positionDifference.y(), modifierKey);
	else if ( button == LEFT_AND_RIGHT_BUTTONS )
		this->mouseController->onMouse_LeftAndRight_Drag(this->camera->getObject().get(), this->positionDifference.x(), this->positionDifference.y(), modifierKey);

	this->mousePosLast.x() = this->mousePosCur.x();
	this->mousePosLast.y() = this->mousePosCur.y();
}

void Sx::Interface::ViewController::mouseWheelEvent(int delta) {
	this->mouseController->onMouse_Wheel_Rotate(this->camera->getObject().get(), delta);
}

void Sx::Interface::ViewController::mouseEnterEvent() {
	return;
}

void Sx::Interface::ViewController::mouseExitEvent() {
	return;
}
